package com.benshou.bcss.common.lambda;

import com.benshou.bcss.common.j17common.annotation.NotNull;
import com.benshou.bcss.common.j17common.annotation.Nullable;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import lombok.NonNull;
import lombok.SneakyThrows;

import java.io.*;
import java.lang.invoke.MethodHandleProxies;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Executable;
import java.lang.reflect.Proxy;
import java.util.Objects;

/**
 * Lambda 工具类
 *
 * @author <a href="mailto:liuzhenbang@benshou.com">Liu Zhenbang</a>
 */
public class Lambdas {

    /**
     * Lambda 元数据加载器
     */
    private static final CacheLoader<SFunction<?, ?>, LambdaMetadata> LOADER = CacheLoader.from(func -> {
        var implMethodName = getImplMethodName(Objects.requireNonNull(func));
        return new _LambdaMetadataImpl(implMethodName, getFieldNameByJavaBeanMethodName(implMethodName));});

    /**
     * 映射：Function -> Lambda 元数据
     */
    private static final LoadingCache<SFunction<?, ?>, LambdaMetadata> CACHE = CacheBuilder.newBuilder()
            .weakKeys()
            .softValues()
            .build(LOADER);

    /**
     * 根据 Func ，加载字段名
     *
     * @param func Func
     * @param followJavaBean 是否按 JavaBean 规范
     * @return 字段名
     * @param <T> Lambda 参数的类型
     */
    @SneakyThrows(Exception.class)
    @Nullable
    public static <T> String loadFieldName(@NonNull SFunction<T, ?> func, boolean followJavaBean) {
        var metadata = LOADER.load(func);
        return followJavaBean ? metadata.fieldNameOfJavaBean() : metadata.getImplMethodName();
    }

    /**
     * 根据 Func ，加载字段名（按 JavaBean 规范）
     *
     * @param func Func
     * @return 字段名
     * @param <T> Lambda 参数的类型
     */
    @Nullable
    public static <T> String loadFieldName(@NonNull SFunction<T, ?> func) {
        return loadFieldName(func, true);
    }

    /**
     * （带缓存）根据 Func ，获取或加载字段名
     *
     * @param func Func
     * @param followJavaBean 是否按 JavaBean 规范
     * @return 字段名
     * @param <T> Lambda 参数的类型
     */
    @Nullable
    public static <T> String getOrLoadFieldName(@NonNull SFunction<T, ?> func, boolean followJavaBean) {
        var metadata = CACHE.getUnchecked(func);
        return followJavaBean ? metadata.fieldNameOfJavaBean() : metadata.getImplMethodName();
    }

    /**
     * （带缓存）根据 Func ，获取或加载字段名（按 JavaBean 规范）
     *
     * @param func Func
     * @return 字段名
     * @param <T> Lambda 参数的类型
     */
    @Nullable
    public static <T> String getOrLoadFieldName(@NonNull SFunction<T, ?> func) {
        return getOrLoadFieldName(func, true);
    }

    /**
     * 获取 Lambda 实现的方法名
     *
     * @param func Func
     * @return 方法名
     */
    private static String getImplMethodName(@NotNull SFunction<?, ?> func) {
        // 1. IDEA 调试模式下 lambda 表达式是一个代理
        if (func instanceof Proxy) {
            var dmh = MethodHandleProxies.wrapperInstanceTarget(func);
            var executable = MethodHandles.reflectAs(Executable.class, dmh);
            return executable.getName();
        }

        // 2. 反射读取
        try {
            var method = func.getClass().getDeclaredMethod("writeReplace");
            method.setAccessible(true);
            return ((java.lang.invoke.SerializedLambda) method.invoke(func)).getImplMethodName();
        } catch (Throwable e) {
            // 3. 反射失败使用序列化的方式读取
            try (var bos = new ByteArrayOutputStream();
                 var oos = new ObjectOutputStream(bos)) {
                oos.writeObject(func);
                oos.flush();
                try (ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray())) {
                    @Override
                    protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
                        Class<?> cls = super.resolveClass(desc);
                        return cls == java.lang.invoke.SerializedLambda.class ?
                                SerializedLambda.class :
                                cls;
                    }
                }) {
                    return ((SerializedLambda) ois.readObject()).getImplMethodName();
                }
            } catch (IOException | ClassNotFoundException ex) {
                throw new RuntimeException(e);
            }
        }
    }

    /**
     * 根据 JavaBean 规范，根据方法名获取对应的字段名
     *
     * @param methodName 方法名
     * @return 字段名（方法名非 JavaBean 规范时，返回 null ）
     */
    @Nullable
    private static String getFieldNameByJavaBeanMethodName(@NotNull String methodName) {
        int len = methodName.length();
        int preLen;
        if (methodName.startsWith("is") && len > (preLen = 2) ||
                ((methodName.startsWith("get") ||  methodName.startsWith("set")) && len > (preLen = 3))) {
            var first = methodName.charAt(preLen);
            if (first < 'a' || first > 'z') {
                var lower = Character.toLowerCase(first);
                return len == preLen + 1 ? String.valueOf(lower) : lower + methodName.substring(preLen + 1);
            }
        }

        return null;
    }

    /**
     * Lambda 元数据
     *
     * @param implMethodName 实现的方法名
     * @param fieldNameOfJavaBean JavaBean 规范的方法名
     */
    private record _LambdaMetadataImpl(
            @NotNull String implMethodName,
            String fieldNameOfJavaBean) implements LambdaMetadata {

        @Override
        public String getImplMethodName() {
            return this.implMethodName;
        }
    }
}
